#!/bin/bash
# Copyright (C) 2024 Checkmk GmbH - License: GNU General Public License v2
# This file is part of Checkmk (https://checkmk.com). It is subject to the terms and
# conditions defined in the file COPYING, which is part of this source code package.

PIDFILE="${OMD_ROOT}/tmp/run/rabbitmq.pid"
RABBITMQ_NODENAME="rabbit-${OMD_SITE}@localhost"
RABBITMQ_LOG_BASE="${OMD_ROOT}/var/log/rabbitmq"
RABBITMQ_MNESIA_BASE="${OMD_ROOT}/var/rabbitmq/mnesia"
RABBITMQ_CONFIG_FILES="${OMD_ROOT}/etc/rabbitmq/conf.d"
RABBITMQ_CONFIG_MANAGEMENT_IP_FILE="${RABBITMQ_CONFIG_FILES}/04-management-ip.conf"
RABBITMQ_ADVANCED_CONFIG_FILE="${OMD_ROOT}/etc/rabbitmq/advanced_conf.d/00-advanced.conf"
RABBITMQ_ENABLED_PLUGINS_FILE="${OMD_ROOT}/etc/rabbitmq/enabled_plugins"
RABBITMQ_DIST_PORT="${CONFIG_RABBITMQ_DIST_PORT:?}"
RABBITMQ_PORT="${CONFIG_RABBITMQ_PORT:?}"
RABBITMQ_DIAGNOSTICS="${OMD_ROOT}/lib/rabbitmq/sbin/rabbitmq-diagnostics"
RABBITMQCTL="${OMD_ROOT}/lib/rabbitmq/sbin/rabbitmqctl"
BROKER_CERTIFICATES=$OMD_ROOT/bin/message-broker-certs
EXECUTE="${OMD_ROOT}/lib/rabbitmq/sbin/rabbitmq-server"
ERL_EPMD_ADDRESS="::1"
# Control number of Erlang schedulers and amount of memory allocated for constant data
RABBITMQ_SERVER_ADDITIONAL_ERL_ARGS="+S 2:2 +MIscs 32"
# Control the number of arenas that the erlang memory allocator can use for multi-threaded applications
MALLOC_ARENA_MAX="1"
INITLOG="${RABBITMQ_LOG_BASE}/init.log"

services_needing_rabbitmq() {
    printf "%s\n" "piggyback-hub"
}

getpid() {
    cat "${1}" 2>/dev/null
}

process_is_running() {
    # when the process is killed, and the pidfile already removed
    # the process can still run for a short time.
    kill -0 "$1" 2>/dev/null
}

await_process_stop() {
    max=$(("${1}" * 10))
    for N in $(seq "${max}"); do
        process_is_running "$2" || return 0
        [ $((N % 10)) -eq 0 ] && printf "."
        sleep 0.1
    done
    return 1
}

rabbitmq_accepting_connections() {
    "${RABBITMQ_DIAGNOSTICS}" check_port_listener --node "${RABBITMQ_NODENAME}" "${RABBITMQ_PORT}"
    # no idea why we can't use this anymore; it will fail for the management plugin
    # "${RABBITMQ_DIAGNOSTICS}" check_port_connectivity --node "${RABBITMQ_NODENAME}"
}

await_process_start() {
    # Only write output in the end, otherwise we clobber the logfile.
    max=$(("${1}" * 10))
    # If the PIDFILE gets filled, rabbitmq started
    for N in $(seq "${max}"); do
        [ -s "${PIDFILE}" ] && rabbitmq_accepting_connections &>/dev/null && break
        [ -e "${PIDFILE}" ] || {
            date +"%F %T.%N RabbitMQ failed to start (pidfile vanished)"
            return 1
        }
        sleep 0.1
    done

    # once again, mostly to get the output of the diagnostics call
    [ -s "${PIDFILE}" ] && rabbitmq_accepting_connections && {
        date +"%F %T.%N RabbitMQ considered ready"
        return 0
    }

    date +"%F %T.%N RabbitMQ failed to start (giving up after timeout)"
    return 1
}

force_kill() {
    printf "sending SIGKILL."
    kill -9 "${1}"
}

exit_successfully() {
    printf "%s\n" "${1}"
    exit 0
}

exit_failure() {
    printf "%s\n" "${1}"
    exit 1
}

ensure_broker_certs() {
    printf "Ensuring broker certificates presence...\n"
    if [ ! -f "${OMD_ROOT}/etc/rabbitmq/ssl/cert.pem" ]; then
        printf "Creating broker certificates...\n"
        "${BROKER_CERTIFICATES}" "${OMD_ROOT}" "${OMD_SITE}"
    fi
}

config_mgmt_listener() {
    printf "Write management listener ip...\n"

    listener_ip="127.0.0.1"
    if ping -c1 ::1 &>/dev/null; then
        listener_ip="::1"
    fi

    cat <<HERE >"${RABBITMQ_CONFIG_MANAGEMENT_IP_FILE}"
# Management API for self monitoring
# Ip set by rabbitmq init script etc/init.d/rabbitmq. Better do not edit manually.
management.tcp.ip = ${listener_ip}
HERE

}

stop_and_reset_app() {
    "${RABBITMQCTL}" -n "${RABBITMQ_NODENAME}" stop_app &>/dev/null
    "${RABBITMQCTL}" -n "${RABBITMQ_NODENAME}" reset &>/dev/null
}

start_app() {
    "${RABBITMQCTL}" -n "${RABBITMQ_NODENAME}" start_app &>/dev/null
}

stop_process() {
    stop_and_reset_app
    printf "killing %s..." "${1}"
    kill "${1}" 2>/dev/null
}

required_by_local_services() {
    while read -r service; do
        omd -v status -b "${service}" | grep -q "^${service} 5$" || return 0
    done <<<"$(services_needing_rabbitmq)"
    return 1
}

is_enabled() {
    required_by_local_services
}

is_enabled || exit 5

case "$1" in

    start-blocking)
        ensure_broker_certs
        config_mgmt_listener

        RABBITMQ_NODENAME="${RABBITMQ_NODENAME}" \
            RABBITMQ_ENABLED_PLUGINS_FILE="${RABBITMQ_ENABLED_PLUGINS_FILE}" \
            RABBITMQ_CONFIG_FILES="${RABBITMQ_CONFIG_FILES}" \
            RABBITMQ_ADVANCED_CONFIG_FILE="${RABBITMQ_ADVANCED_CONFIG_FILE}" \
            RABBITMQ_LOG_BASE="${RABBITMQ_LOG_BASE}" \
            RABBITMQ_PID_FILE="${PIDFILE}" \
            RABBITMQ_MNESIA_BASE="${RABBITMQ_MNESIA_BASE}" \
            RABBITMQ_DIST_PORT="${RABBITMQ_DIST_PORT}" \
            ERL_EPMD_ADDRESS="${ERL_EPMD_ADDRESS}" \
            RABBITMQ_SERVER_ADDITIONAL_ERL_ARGS="${RABBITMQ_SERVER_ADDITIONAL_ERL_ARGS}" \
            MALLOC_ARENA_MAX="${MALLOC_ARENA_MAX}" \
            flock "${PIDFILE}" --command "${EXECUTE}"
        rm --force "${PIDFILE}" # should be removed by rabbitmq itself
        ;;

    start)
        printf "Starting rabbitmq..."
        if ! flock --nonblock "${PIDFILE}" --command :; then
            exit_successfully 'already running.'
        fi

        # set locale to avoid irrelevant warnings in log
        export LANG="C.utf8"
        export LC_CTYPE="C.utf8"

        touch "${PIDFILE}"
        mkdir -p "${RABBITMQ_LOG_BASE}"

        nohup "${0}" "start-blocking" &>"${INITLOG}" &

        # try for one minute to interpret the outcome
        if await_process_start 60 &>>"${INITLOG}"; then
            RABBITMQ_NODENAME="${RABBITMQ_NODENAME}" \
                RABBITMQCTL="${RABBITMQCTL}" \
                cmk-monitor-broker --setup &>>"${INITLOG}"
            exit_successfully "OK"
        fi

        # Give up
        exit_failure "failed"
        ;;

    stop)
        printf "Stopping rabbitmq..."
        PID="$(getpid "${PIDFILE}")"

        if [ -z "${PID}" ]; then
            exit_successfully "not running"
        fi

        if ! kill -0 "${PID}" >/dev/null 2>&1; then
            rm "${PIDFILE}"
            exit_successfully "not running (PID file orphaned)"
        fi

        stop_process "${PID}"

        # signal could be sent. Patiently wait for max 1 minute.
        if await_process_stop 60 "${PID}"; then
            exit_successfully "OK"
        fi

        # We insist...
        force_kill "${PID}"
        if await_process_stop 10 "${PID}"; then
            exit_successfully "OK"
        fi
        # Give up
        exit_failure "failed"
        ;;

    reload)
        stop_and_reset_app
        start_app
        ;;

    status)
        printf "Checking status of rabbitmq..."
        PID="$(getpid "${PIDFILE}")"

        if [ -z "${PID}" ]; then
            exit_failure "not running"
        fi

        if ! kill -0 "${PID}" 2>/dev/null; then
            exit_failure "not running (PID file orphaned)"
        fi
        exit_successfully "running"
        ;;
    *)
        exit_failure "Usage: rabbitmq {start|stop|reload|status}"
        ;;

esac
